<?php
/**
 * @author andery <andery@foxmail.com>
 * @link http://www.minophp.com/
 * @version $Id: CModel.php 2009-08-09 16:50:18Z andery $
 */
/* 模型相关常量定义 */
define('HAS_ONE', 1);                     //一对一关联
define('BELONGS_TO', 2);                  //属于关联
define('HAS_MANY', 3);                    //一对多关联
define('HAS_AND_BELONGS_TO_MANY', 4);     //多对多关联

class CModel
{
    protected $db = null;
    // 所映射的数据库表 
    protected $table = '';
    // 主键 
    protected $prikey= '';
    // 别名 
    protected $alias = '';
    // 数据信息
    protected $data = array();
    // 数据验证规则
    protected $_autov = array();
    // 模型的名称 
    protected $_name   = '';
    // 与其他模型的关系
    protected $_relation = array();
    // 表前缀 
    protected $_prefix = '';
    // 最近一次的错误信息
    protected $_error = '';    
    
	public function __construct()
	{
        // 数据库连接信息
        $this->db = CDb::getInstance();
        !$this->alias && $this->alias = $this->table;
    }
    
    /**
     * 获取模型名称
     * @access public
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }
    
    /**
     * 获取数据表名称
     * @access public
     * @return string
     */
    public function getTable()
    {
    	return $this->table;
    }
    
    /**
     * 设置数据对象的值
     * @param string $name 名称
     * @param mixed $value 值
     * @return void
     */
    public function __set($name, $value)
    {
    	$this->data[$name] = $value;
    }
    
    /**
     * 获取数据对象的值
     * @param string $name 名称
     * @return mixed
     */
    public function __get($name)
    {
        return isset($this->data[$name])?$this->data[$name]:null;
    }
    
    public function getError()
    {
        return $this->_error;
    }
    
    /**
     * 删除记录
     * @param mixed $conditions 避免漏写参数导致全部删除全部删除用$conditions='all'
     * @return int 删除行数
     */   
    public function delete($conditions='')
    {
        if (empty($conditions)) {
            return;
        }
        if ($conditions === 'all') {
            $conditions = '';
        } else {
            $conditions = $this->_parseWhere($conditions, false);
        }
        $this->db->query("DELETE FROM {$this->table}{$conditions}");
        return $this->db->affected_rows();
    }
    
    // 删除关联信息
    private function _deleteDependent()
    {
        
    }
    
    private function getAll($sql)
    {
        return $this->db->getAll($sql);
    }
    
    /**
     * 查找数据
     * @param mixed $params 
     */
    public function find($params=array())
    {
        $sql = 'SELECT ';
        $init_params = array(
            'distinct'=>isset($params['distinct']) ? $this->_parseDistinct($params['distinct']) : '',
            'fields'=>isset($params['fields']) ? $this->_parseFields($params['fields']) : '*',
            'table'=>'FROM ' . $this->table . ' ' . $this->alias,
            'join'=>isset($params['join']) ? $this->_parseJoin($params['join']) : '',
            'where'=>isset($params['where']) ? $this->_parseWhere($params['where'], true) : '', 
            'group'=>isset($params['group']) ? $this->_parseGroup($params['group'], true) : '',
            'having'=>isset($params['having']) ? $this->_parseHaving($params['having'], true) : '',
            'order'=>isset($params['order']) ? $this->_parseOrder($params['order'], true) : '',
            'limit'=>isset($params['limit']) ? $this->_parseLimit($params['limit']) : ''
        );
        $sql .= implode(' ', $init_params);
        return $this->db->getAll($sql);
    }
    
    /**
     * Distinct分析
     * @param bool $distinct
     */
    private function _parseDistinct($distinct)
    {
        return !empty($distinct) ? 'DISTINCT' : '';
    }
    
    /**
     * 分析要查询的字段名
     * @param mixed $fields
     *    $fields = 'this.fields1,mode.fields2'
     *    $fields = arrray('f1'=>'this.fields1', 'f2'=>'mode.fields2') orther...
     */
    private function _parseFields($fields)
    {
    	if (is_array($fields)) {
    	    foreach ($fields as $key=>$value) {
    	        if (!is_numeric($key)) {
    	            $fieldsArr[] = $value .' AS ' . $key;
    	        } else {
    	            $fieldsArr[] = $value;
    	        }
    	    }
    	    $fieldsStr = implode(',', $fieldsArr);
    	} elseif (is_string($fields)) {
    	    $fieldsStr = $fields;
    	} else {
    	    $fieldsStr = '*';
    	}
    	$fieldsStr = preg_replace('/([a-zA-Z0-9_]+)\.([a-zA-Z0-9_*]+)/e', "\$this->_getFieldTable('\\1') . '.\\2'", $fieldsStr);
    	return $fieldsStr;
    }
    
    /**
     * 获取字段所属的表名
     * @param string $mode 模型名称
     */
    private function _getFieldTable($mode)
    {
        if ($mode=='this') {
            return $this->alias;
        } else {
            $mode = Nimo::app()->loadModel($mode);
            return $mode->alias;
        }
    }
    
    /**
     * 表关联操作
     * @param string $join 关系名称
     */
    private function _parseJoin($join)
    {
        $ext_limit = '';
        $relation = $this->_getRelation($join);
        switch ($relation['type']) {
            case HAS_ONE : //一对一关系
                $join_model = Nimo::app()->loadModel($relation['model']);
                isset($relation['ext_limit']) && $ext_limit = ' AND ' . $this->_getExtLimit($relation['ext_limit'], $join_model->alias);
                $refer_key = isset($relation['refer_key']) ? $relation['refer_key'] : $this->prikey;
                return "LEFT JOIN {$join_model->table} {$join_model->alias} ON " .
                		"{$this->alias}.{$refer_key}={$join_model->alias}.{$relation['foreign_key']}{$ext_limit}";
                break;
            case BELONGS_TO : //属于关系与拥有是一个反向的关系
                $join_model = Nimo::app()->loadModel($relation['model']);
                $be_related = $join_model->_getRelation($relation['reverse']);
                isset($relation['ext_limit']) && $ext_limit = ' AND ' . $this->_getExtLimit($relation['ext_limit'], $join_model->alias);
                $refer_key = isset($be_related['refer_key']) ? $be_related['refer_key'] :$join_model->prikey ;
                return "LEFT JOIN {$join_model->table} {$join_model->alias} ON " .
                		"{$this->alias}.{$be_related['foreign_key']} = {$join_model->alias}.{$refer_key}{$ext_limit}";
                break;
            case HAS_AND_BELONGS_TO_MANY : //连接中间表，本表主键=中间表外键
                $malias = isset($relation['alias']) ? $relation['alias'] : $relation['middle_table'];
                isset($relation['ext_limit']) && $ext_limit = ' AND ' . $this->_getExtLimit($relation['ext_limit'], $malias);
                return "LEFT JOIN {$this->_prefix}{$relation['middle_table']} {$malias} ON " .
                		"{$this->alias}.{$this->prikey} = {$malias}.{$relation['foreign_key']}{$ext_limit}";
                break;
        }
    }
    
    private function _getRelation($rkey=null)
    {
        if (is_null($rkey)) {
            return $this->_relation;
        } elseif (array_key_exists($rkey, $this->_relation)) {
            return $this->_relation[$rkey];
        } else {
            throw new CException(Nimo::lang('not_found_relation'));
        }
    }
    
    private function _getExtLimit($ext_limit, $alias = null)
    {
        if (!$ext_limit) return false;
        $str = '';
        !empty($alias) && $pre = "{$alias}.";
        foreach ($ext_limit as $_k => $_v) {
            $str .=  $str == '' ? " {$pre}{$_k} = '{$_v}'" : " AND {$pre}{$_k} = '{$_v}'";
        }
        return $str;
    }
    
    /**
     * 条件整理
     * @param mixed   $conditions
     * @return string
     */
    private function _parseWhere($conditions,$is_alias=false)
    {
        $alias = !($is_alias) ? '' : $alias = $this->alias . '.';
        if (is_numeric($conditions)) {
            /*数字则默认为主键值*/
            $conditionsStr = "WHERE {$alias}{$this->prikey} = {$conditions}";
        } elseif (is_string($conditions)) {
            /*直接定义语句*/
            $conditionsStr = "WHERE {$conditions}";
        } elseif (is_array($conditions)) {
            /* 如果是数组，则认为其是一个主键集合 */
            if(empty($conditions)) return '';
            $conditionsStr = '';
            foreach ($conditions as $key=>$value) {
            	if (!$value) {
                    unset($conditions[$key]);
                }
            }
            $conditions = array_unique($conditions);
            $zone = implode(',', $conditions);
            $conditionsStr = 'WHERE {$alias}{$this->prikey} IN(' . $zone . ')';
        } else {
            $conditionsStr = '';
        }
        return $conditionsStr;
    }
    
    private function _parseGroup($group, $is_alias=false)
    {
        $alias = !($is_alias) ? '' : $alias = $this->alias . '.';
        return !empty($group) ? "GROUP BY {$alias}{$group}" : '';
    }
    
    private function _parseHaving($having, $is_alias=false)
    {
        $alias = !($is_alias) ? '' : $alias = $this->alias . '.';
        return !empty($having) ? "HAVING {$alias}{$having}" : '';
    }
    
    private function _parseOrder($order, $is_alias=false)
    {
        $alias = !($is_alias) ? '' : $alias = $this->alias . '.';
        return !empty($order) ? "ORDER BY {$alias}{$order}" : '';
    }
    
    private function _parseLimit($limit) {
        return !empty($limit) ? 'LIMIT '.$limit : '';
    }
    
    /**
     *  添加一条记录
     * @param  array $data 字段和对应的值 为空则使用当前数据对象保存的值
     * @param  bool $compatible 标识REPLACE和INSERT
     * @return int 所添加的最后ID 
     */
    public function add($data='', $compatible = false)
    {
    	if (empty($data)) {
            if(!empty($this->data)) {
                $data = $this->data;
            }else{
                $this->_error = Nimo::lang('no_valid_data');
                return false;
            }
        }
        $insert_multi = $this->_insertMulti($data);
        $mode = $compatible ? 'REPLACE' : 'INSERT';

        $this->db->query("{$mode} INTO {$this->table}{$insert_multi['fields']} VALUES{$insert_multi['values']}");
        $insert_id = $this->db->insert_id();
        if ($insert_id) {
            if ($insert_multi['length'] > 1) {
                for ($i = $insert_id; $i < $insert_id + $insert_multi['length']; $i++) {
                    $id[] = $i;
                }
            } else {
                /* 添加单条记录 */
                $id = $insert_id;
            }
        }
        return $id;
    }
    
    /**
     *  获取插入的数据SQL
     *  @param  array $data
     *  @return string
     */
    private function _insertMulti($data)
    {
        $fields = array();
        $values = array();
        $length = 1;
        if (is_array($data[0])) {
            $length = count($data);
            foreach ($data as $_k => $_v)
            {
                foreach ($_v as $_f => $_fv)
                {
                    $is_array = is_array($_fv);
                    ($_k == 0 && !$is_array) && $fields[] = $_f;
                    !$is_array && $values[$_k][] = "'{$_fv}'";
                }
                $values[$_k] = '(' . implode(',', $values[$_k]) . ')';
            }
        } else {
            foreach ($data as $_k => $_v) {
                $is_array = is_array($_v);
                !$is_array && $fields[] = $_k;
                !$is_array && $values[] = "'{$_v}'";
            }
            $values = '(' . implode(',', $values) . ')';
        }
        $fields = '(' . implode(',', $fields) . ')';
        is_array($values) && $values = implode(',', $values);

        return compact('fields', 'values', 'length');
    }
    
    /**
     * 更新数据
     * @param mixed $conditions 条件限定
     * @param array $save_data 数据数组
     * @return int 影响条数
     */
    public function update($update_data='', $conditions='')
    {
        if (empty($update_data)) {
            if (!empty($this->data)) {
                $save_data = $this->data;
            } else {
                return false;
            }
        }
        //数据验证
        //$edit_data = $this->_valid($edit_data);
        if (!$update_data) {
            return false;
        }

        $update_multi = $this->_getUpdateMulti($update_data);
        $conditions  = $this->_parseWhere($conditions);
        $this->db->query("UPDATE {$this->table} SET {$update_multi}{$conditions}");
        return $this->db->affected_rows();
    }
    
    /**
     *  获取设置字段
     *  @param  array $data
     *  @return string
     */
    private function _getUpdateMulti($data)
    {
        if (!is_array($data))
        {
            return $data;
        }
        $fields = array();
        foreach ($data as $_k => $_v)
        {
            !is_array($_v) && $fields[] = "{$_k}='{$_v}'";
        }

        return implode(',', $fields);
    }
}